package com.xiaomo.gitee.datamanager.aspect.log;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xiaomo.gitee.datamanager.aspect.log.anno.SysLog;
import com.xiaomo.gitee.datamanager.aspect.log.service.impl.SysLogServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Component
@Aspect
@Slf4j
@RequiredArgsConstructor
public class SysLogAspect {

    private final SysLogServiceImpl sysLogService;

    @Pointcut("@annotation(com.xiaomo.gitee.datamanager.aspect.log.anno.SysLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object afterLog(ProceedingJoinPoint joinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long time = System.currentTimeMillis() - begin;
        save(joinPoint, time);
        return result;
    }

    private void save(ProceedingJoinPoint joinPoint, long time){
        MethodSignature methodSignature = ((MethodSignature) joinPoint.getSignature());
        Method method = methodSignature.getMethod();
        SysLogBO sysLogBO = new SysLogBO();
        SimpleDateFormat format = new SimpleDateFormat("yyyy MM/dd hh:mm:ss");

        sysLogBO.setCreateData(format.format(new Date()));
        sysLogBO.setClassName(joinPoint.getTarget().getClass().getName());
        sysLogBO.setMethodName(method.getName());
        sysLogBO.setExecuteTime(time);
        sysLogBO.setMsg(method.getAnnotation(SysLog.class).value());
        if (method.getAnnotation(SysLog.class).operator().equalsIgnoreCase("user")) {
            sysLogBO.setUsername(getUsername());
        } else {
            sysLogBO.setUsername("系统");
        }
        sysLogBO.setCustomMsg(JSONUtil.parseObj(MDC.getCopyOfContextMap()));
        sysLogBO.setAnnotationCustomMsg(getAnnotationMsg(joinPoint));

        MDC.clear();
        sysLogService.save(sysLogBO);
    }

    private String getUsername() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return (authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken)) ?
                authentication.getName() : "匿名用户";
    }

    private JSONObject getAnnotationMsg(ProceedingJoinPoint joinPoint){
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String msg = method.getAnnotation(SysLog.class).extendMsg();
        if (msg == null || msg.isBlank() || msg.isEmpty())
            return null;
        String regex = "#\\{[\\w.]+\\}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(msg);
        Map<String, Object> params = getParams(joinPoint);
        while (matcher.find()) {
            String content = matcher.group();
            String temp = content.replace("#{", "").replace("}", "");
            if (content.contains(".")) {
                String[] split = temp.split("\\.");
                Object value = getValue(params, split[0]);
                Field field;
                try {
                    assert value != null;
                    field = value.getClass().getDeclaredField(split[1]);
                } catch (Exception e) {
                    continue;
                }
                field.setAccessible(true);
                try {
                    value = field.get(value);
                } catch (IllegalAccessException e) {
                    continue;
                }
                field.setAccessible(false);
                if (value == null)
                    continue;
                msg = msg.replace(content, value.toString());
            } else {
                Object value = getValue(params, temp);
                if (value == null)
                    continue;
                msg = msg.replace(content, value.toString());
            }
        }
        return JSONUtil.parseObj(msg);
    }

    private Object getValue(Map<String, Object> map, String paramName){
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getKey().equals(paramName)) {
                return entry.getValue();
            }
        }
        return null;
    }

    private Map<String, Object> getParams(JoinPoint joinPoint) {
        Map<String, Object> params = new HashMap<>(5);
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] names = signature.getParameterNames();
        for (int i = 0; i < args.length; i++) {
            params.put(names[i], args[i]);
        }
        return params;


    }
}
